﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;

namespace BinaryXmlInspector
{
    public class WcfBinaryCodec
    {
        public WcfBinaryCodec() 
        { }

        public WcfBinaryCodec(Encoding encoding)
        {
            m_encoding = encoding;
        }

        Encoding m_encoding = Encoding.UTF8;

        /// <summary>
        /// Decode a bytestream that was encoded by WCF's BinaryEncodingBindingElement.  Will throw if the bytestream does
        /// not decode properly or the result is not valid XML.  I/O streams are flushed but not closed.
        /// </summary>        
        /// <param name="explodeNewlines">if true, the returned string will be nicely indented according to 
        /// element depth, and each attribute will be placed on its own line</param>
        /// <returns></returns>
        public void DecodeBinaryXML(Stream binaryInput, Stream xmlOutput, bool? explodeNewlines)
        {
            // defaults
            var explode = explodeNewlines ?? false;

            // parse bytestream into the XML DOM
            var doc = new XmlDocument();
            using (var binaryReader = XmlDictionaryReader.CreateBinaryReader(binaryInput, WcfDictionaryBuilder.Dict, XmlDictionaryReaderQuotas.Max))
            {
                doc.Load(binaryReader);
            }
                    
            // write document to the output stream with customized settings
            var settings = new XmlWriterSettings()
            {
                CheckCharacters = false,
                CloseOutput = false,
                ConformanceLevel = ConformanceLevel.Auto,
                Encoding = m_encoding,
                Indent = explode,
                IndentChars = "\t",
                NewLineChars = Environment.NewLine,
                NewLineHandling = explode ? NewLineHandling.Replace : NewLineHandling.None,
                NewLineOnAttributes = explode
                // QuoteChar = '"' 
            };
            using (var writer = XmlWriter.Create(xmlOutput, settings))
            {
                doc.Save(writer);
                writer.Flush();
                xmlOutput.Flush();
            }
        }

        public string DecodeBinaryXML(byte[] binaryInput, bool? explodeNewLines)
        {
            var input = new MemoryStream(binaryInput);
            var output = new MemoryStream();
            DecodeBinaryXML(input, output, explodeNewLines);
            output.Seek(0, SeekOrigin.Begin);
            return new StreamReader(output, m_encoding).ReadToEnd();
        }

        /// <summary>
        /// Encode a text stream into a binary XML stream compatible with WCF's BinaryEncodingBindingElement.  Will throw if 
        /// the input stream cannot be parsed into an XML document.  I/O streams are flushed but not closed.
        /// </summary>
        /// <param name="xmlInput"></param>
        /// <param name="binaryOutput"></param>
        public void EncodeBinaryXML(Stream xmlInput, Stream binaryOutput)
        {
            // parse string into the XML DOM
            var doc = new XmlDocument();
            doc.Load(xmlInput);

            // write bytestream
            using (var binaryWriter = XmlDictionaryWriter.CreateBinaryWriter(binaryOutput, WcfDictionaryBuilder.Dict, null, false))
            {
                doc.Save(binaryWriter);
                binaryWriter.Flush();
                binaryOutput.Flush();
            }
        }

        public byte[] EncodeBinaryXML(string xmlInput)
        {
            var input = new MemoryStream(m_encoding.GetBytes(xmlInput));
            var output = new MemoryStream();
            EncodeBinaryXML(input, output);
            return output.ToArray();
        }
    }
}
